﻿using System.Drawing;

namespace Rsdn.Janus
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reactive.Disposables;
    using System.Windows.Forms;
    using BLToolkit.Data.Linq;
    using BLToolkit.Mapping;
    using DataModel;
    using JetBrains.Annotations;
    using MultiLang;
    using ObjectModel;
    using SmartApp;
    using TreeGrid;

    /// <summary>
    /// Отражает отдельный форум.
    /// </summary>
    public class Forum : Feature, IMessagesFeature, IGetData, IForum
    {
        #region dbfields
        private int id;
        private string name;
        private int lastSync;
        private bool isSubscribed;
        private bool isRateable;
        private bool rateLimit;
        private int messagesCount;
        private int unread;
        private bool inTop;
        private int priority;
        private string forumDescription;
        #endregion

        private readonly IServiceProvider _serviceProvider;
        private readonly IForumImageManager _imageManager;


        /// <summary>Ссылка на корень форума (типа Msg).</summary>
        private readonly WeakReference _weakMsgRoot = new WeakReference(null);
        private int _activeMsgId = -1;
        private readonly WeakReference _activeMsg = new WeakReference(null);

        /// <summary>
        /// Если false, значит сообщения форума загружены не полностью.
        /// </summary>
        private bool _isLoadAll = false;

        public event EventHandler OnBeforeLoadData;

        public Forum(IServiceProvider provider)
        {
            _serviceProvider = provider;
            _imageManager = ServiceProvider.GetRequiredService<IForumImageManager>();
        }

        public IDisposable ActivateLiveBehavior()
        {
            Features.Instance.OnBeforeFeatureActivate += InstanceBeforeFeatureActivate;
            Action dispose = delegate { Features.Instance.OnBeforeFeatureActivate -= InstanceBeforeFeatureActivate; };
            return Disposable.Create(dispose);
        }

        #region db properties
        [MapField("id")]
        public int ID
        {
            get { return id; }
            set { id = value; }
        }


        [MapField("name")]
        public string Name
        {
            get { return name; }
            set { name = value; }
        }


        [MapField("lastSync")]
        public int LastSync
        {
            get { return lastSync; }
            set { lastSync = value; }
        }


        [MapField("subscribed")]
        public bool IsSubscribed
        {
            get { return isSubscribed; }
            set { isSubscribed = value; }
        }


        [MapField("rated")]
        public bool IsRateable
        {
            get { return isRateable; }
            set { isRateable = value; }
        }


        [MapField("ratelimit")]
        public bool RateLimit
        {
            get { return rateLimit; }
            set { rateLimit = value; }
        }


        [MapField("messagesCount"), Nullable]
        public int MessagesCount
        {
            get { return messagesCount; }
            set { messagesCount = value; }
        }

        [MapField("repliesToMeUnread"), Nullable]
        public int RepliesToMeUnread { get; set; }



        [MapField("unread"), Nullable]
        public int Unread
        {
            get { return unread; }
            set { unread = value; }
        }


        [MapField("intop"), Nullable]
        public bool InTop
        {
            get { return inTop; }
            set { inTop = value; }
        }

        [MapField("priority"), Nullable]
        public int Priority
        {
            get { return priority; }
            set { priority = value; }
        }


        [MapField("descript")]
        public string ForumDescription
        {
            get { return forumDescription; }
            set { forumDescription = value; }
        }
        #endregion

        [MapIgnore]
        public string DisplayName
        {
            get
            {
                return Config.Instance.ForumDisplayConfig.ShowFullForumNames ? Description : Name;
            }
        }

        [MapIgnore]
        public override string Description
        {
            get
            {
                return ForumDescription;
            }
        }


        public override string ToString()
        {
            return DisplayName + " " + string.Format(                 //MLHIDE
                                        SR.Forum.DisplayName, MessagesCount, Unread, RepliesToMeUnread);
        }

        private void InstanceBeforeFeatureActivate(
            IFeature oldFeature, IFeature newFeature, ref bool cancel)
        {
            if (this == oldFeature && !Config.Instance.RestoreForumPosition)
            {
                ActiveMsg = null;
            }
        }

        #region Интерфейс.
        /// <summary>
        /// Gets the active MSG.
        /// </summary>
        /// <returns></returns>
        protected virtual IMsg GetActiveMsg()
        {
            lock (this)
            {
                IMsg activeMsg = ActiveMsgTarget;

                if (activeMsg != null)
                {
                    return activeMsg;
                }

                if (ActiveMsgId >= 0)
                {
                    activeMsg = FindMsgById(ActiveMsgId);
                    ActiveMsgTarget = activeMsg;

                    return activeMsg;
                }

                //Check for forum ID not message id
                if (Config.Instance.RestoreForumPosition && Config.Instance.IsLastReadMessageInForum(ID))
                {
                    int msgId = Config.Instance.GetLastReadMessageInForum(ID);

                    //if (msgId >= 0)
                    //{
                    //    ActiveMsgId = msgId;
                    //}

                    activeMsg = FindMsgById(msgId);
                    ActiveMsgTarget = activeMsg;

                    return activeMsg;
                }

                return null;
            }
        }

        /// <summary>
        /// Sets the active MSG.
        /// </summary>
        /// <param name="value">The value.</param>
        protected virtual void SetActiveMsg(IMsg value)
        {
            lock (this)
            {
                if (value == null)
                {
                    //ActiveMsgId = -1;
                    ActiveMsgTarget = null;

                    Config.Instance.RemoveLastReadMessageInForum(ID);

                    return;
                }

                if (value == WeakMsgRoot.Target)
                {
                    return;
                }

                IMsg curr = value;
                IMsg root = Msgs;

                while (curr.Parent != null)
                {
                    if (root == curr.Parent)
                    {
                        //ActiveMsgId = value.ID;
                        ActiveMsgTarget = value;
                        Control guiControl = GuiControl;

                        Config.Instance.SetLastReadMessageInForum(ID, ActiveMsgId);
                        return;
                    }

                    curr = curr.Parent;
                }

                string message = string.Format(ml.ml_string(246, "Сообщение '{0}' отсутствует в списке сообщений."), value);

                throw new ApplicationException(message);
            }
        }

        /// <summary>
        /// Gets or sets the active message.
        /// </summary>
        /// <value>The active MSG.</value>
        /// <exception cref="ApplicationException"><c>ApplicationException</c>.</exception>
        [MapIgnore]
        public IMsg ActiveMsg
        {
            get 
            {
                return GetActiveMsg();
            }

            set 
            {
                SetActiveMsg(value);
            }
        }

        [MapIgnore]
        public bool IsAllMsgLoaded
        {
            get { return _isLoadAll; }
        }

        public IMsg LoadAllMsg()
        {
            return ReadMsg(true);
        }

        /// <summary>
        /// Gets the root message for the forum.
        /// All messages here as children.
        /// </summary>
        /// <value>The MSGS.</value>
        [MapIgnore]
        public virtual IMsg Msgs
        {
            get
            {
#if FRM_UPDATE_TRACE
                Trace.WriteLine("Frm:get Msgs");                      //MLHIDE
#endif

                IMsg root = WeakMsgRoot.Target != null
                               ? (IMsg) WeakMsgRoot.Target
                               : ReadMsg(Config.Instance.ForumDisplayConfig.MaxTopicsPerForum <= 0);
                return root;
            }
        }

        /// <summary>
        /// Помечает форум как требующий перечитки.
        /// </summary>
        public override void Refresh()
        {
#if FRM_UPDATE_TRACE
            Trace.WriteLine("Frm:Refresh");                           //MLHIDE
#endif
            RefreshInfo();

            ActiveMsgTarget = null;
            WeakMsgRoot.Target = null;
        }

        /// <summary>
        /// Обновляет информацию о форуме не перечитывая сообщения.
        /// </summary>
        public void RefreshInfo()
        {
#if FRM_UPDATE_TRACE
            Trace.WriteLine("Frm:RefreshInfo");                       //MLHIDE
#endif
            LoadCounters(ID);
            Features.Instance.FeatureChanged(this);
        }

        /// <summary>
        /// Детали реализации FindMsgByID...
        /// </summary>
        /// <param name="idMsg">ID сообщения которое нужно найти.</param>
        /// <returns>Найденая ветка.</returns>
        public virtual IMsg FindMsgById(int idMsg)
        {
            IMsg root = Msgs;
            int? idTopic;
			using (var mgr = _serviceProvider.CreateDBContext())
            {
                Table<IForumMessage> forumMessages = mgr.Messages();
                idTopic =
                    forumMessages
                        .Where(msg => msg.ID == idMsg)
                        .Select(msg => (int?)msg.TopicID)
                        .SingleOrDefault();
            }

            if (!idTopic.HasValue)
            {
                return null;
            }

            if (idTopic.Value == 0)
            {
                idTopic = idMsg;
            }

            // Если сообщения форума загружены не полностью, делаем две 
            // попытки поиска
            for (;;)
            {
                IMsg message = root.Cast<IMsg>().FirstOrDefault(msg => msg.ID == idTopic);

                if (message != null)
                {
                    return message.Flatten().First(msg => msg.ID == idMsg);
                }

                // Если сообщения форума загружены полностью,
                // то ловить больше нечего.
                if (IsAllMsgLoaded)
                {
                    return null;
                }

                // Иначе загружаем все собщения и повторяем поиск.
                root = LoadAllMsg();
            }
        }

        /// <summary>
        /// Загружает данные.
        /// Введено только для использования в наследуемых классах
        /// 'Старый код' не хочется изменять, можно внести новые 'большие' ошибки.
        /// </summary>
        /// <param name="isLoadAll">if set to <c>true</c> [is load all].</param>
        internal virtual void LoadData(bool isLoadAll)
        {
            // do nothing here
        }

        /// <summary>
        /// Читает все сообщения форума не обращая внимание на значение
        /// Config.Instance.MaxTopicsPerForum
        /// </summary>
        /// <param name="isLoadAll">Если установлено <c>true</c> то читать все сообщения.</param>
        /// <returns>список сообщений</returns>
        private IMsg ReadMsg(bool isLoadAll)
        {
            if (OnBeforeLoadData != null)
            {
                OnBeforeLoadData(this, EventArgs.Empty);
            }
#if FRM_UPDATE_TRACE
            Trace.WriteLine("FRM:Forum ReadMsg");                     //MLHIDE
#endif
            _isLoadAll = isLoadAll;

            // На всякий пожарный сбрасываем активное сообщение.
            ActiveMsgTarget = null;

            IMsg root = LoadTopicsAsChildren(isLoadAll);

            WeakMsgRoot.Target = root;

            return root;
        }

        /// <summary>
        /// Loads all topics as children in message.
        /// </summary>
        /// <param name="isLoadAll">if set to <c>true</c> [is load all].</param>
        /// <returns>root message</returns>
        [CanBeNull]
        protected virtual IMsg LoadTopicsAsChildren(bool isLoadAll)
        {
            IMsg root = Msg.GetTopics(ServiceProvider, ID, Config.Instance.ForumSortCriteria, isLoadAll);
            return root;
        }

        #endregion

        #region IGetData

        void IGetData.GetData(NodeInfo nodeInfo, CellInfo[] cellData)
        {
            var styleConfig = Config.Instance.StyleConfig;

            nodeInfo.ForeColor = Priority == 0
                                    ? styleConfig.NavigationTreeForumColor
                                    : styleConfig.ForumPriorityColor[Priority];

            nodeInfo.Highlight = Unread > 0;
            cellData[0].Text = DisplayName;
            cellData[0].CellImageType = CellImageType.Image;
#if FRI_TRACE
            //Trace.WriteLine(string.Format("FRI: Change image unread = {0} {1}.{2}", Unread,ID,DisplayName));
#endif
            Image forumImage = GetForumImage();
            cellData[0].Image = forumImage;

            cellData[1].Text = Info;
        }

        protected virtual Image GetForumImage()
        {
            return _imageManager.GetForumImage(
                Unread > 0,
                InTop,
                RepliesToMeUnread > 0);
        }

        #endregion IGetData

        #region IFeature

        [MapIgnore]
        public override String Info
        {
            get
            {
                return string.Format(
                    " {0}/{1}{2}",                                    //MLHIDE
                    RepliesToMeUnread,
                    Unread,
                    (Config.Instance.ForumDisplayConfig.ShowTotalMessages ? "/" + MessagesCount : string.Empty) //MLHIDE
                    );
            }
        }

        [MapIgnore]
        //string IFeature.Key
        public override string  Key
        {
            get { return "Inbox" + Name; }                            //MLHIDE
        }

        public override bool IsUpdateNeed(object msg)
        {
            // don't need to update this future folder if any message changed. It is already did in an other way
            return false;
        }

        #endregion ITreeNode

        #region IMessagesFeature

        /// <summary>
        /// Активные сообщения.
        /// </summary>
        /// <value></value>
        public virtual IEnumerable<IMsg> ActiveMessages
        {
            get
            {
                return ForumDummyForm.Instance.SelectedMessages;
            }
        }

        /// <summary>
        /// Gets the service provider.
        /// </summary>
        /// <value>The service provider.</value>
        public IServiceProvider ServiceProvider
        {
            get
            {
                return _serviceProvider;
            }
        }

        /// <summary>
        /// Ссылка на корень форума (типа Msg).
        /// </summary>
        /// <value>The weak MSG root.</value>
        protected WeakReference WeakMsgRoot
        {
            get
            {
                return _weakMsgRoot;
            }
        }

        public virtual event EventHandler ActiveMessagesChanged
        {
            add { ForumDummyForm.Instance.SelectedMessagesChanged += value; }
            remove { ForumDummyForm.Instance.SelectedMessagesChanged -= value; }
        }

        #endregion

        #region IFeatureGui

        protected override Control CreateGuiControl()
        {
            return ForumDummyForm.Instance;
        }

        #endregion IFeatureGui

        #region Работа с БД.

        /// <summary>
        /// Loads the counters only.
        /// </summary>
        /// <param name="id">The forum id.</param>
        internal virtual void LoadCounters(int id) //FillForums
        {
#if FRM_UPDATE_TRACE
            Trace.WriteLine(string.Format("FRU: Forum.LoadCounters {0} - {1}", Description, id)); //MLHIDE
#endif
			using (var db = ServiceProvider.CreateDBContext())
            {
                IQueryable<IServerForum> forums = db.ServerForums(f => f.ID == id);
                #region SQL
//-- SET @id1 = 8

//SELECT
//    [f].[ID],
//    [f].[Name],
//    [f].[Descript],
//    [f].[Rated],
//    [f].[InTop],
//    [f].[RateLimit]
//FROM
//    [server_forums] [f]
//WHERE
                //    [f].[ID] = @id1
                #endregion

                var queryable = forums.Select(f =>
                        new
                            {
                                f.ID,
                                f.Name,
                                f.Descript,
                                TopicCount = f.TopicInfos.Count(),
                                AnswersCount = f.TopicInfos.Sum(ti => ti.AnswersCount),
                                Unread = f.TopicInfos.Sum(ti => ti.AnswersUnread),
                                MeUnread = f.TopicInfos.Sum(ti => ti.AnswersToMeUnread)
                            });
                #region SQL
//--  SQLite
//-- DECLARE @id1 Int32

//-- SET @id1 = 52

//SELECT
//    [f].[ID],
//    [f].[Name],
//    [f].[Descript],
//    (
//        SELECT
//            Count(*)
//        FROM
//            [topic_info] [t1]
//        WHERE
//            [f].[ID] = [t1].[gid]
//    ) as [c1],
//    (
//        SELECT
//            Sum([t2].[answers_count])
//        FROM
//            [topic_info] [t2]
//        WHERE
//            [f].[ID] = [t2].[gid]
//    ) as [c3],
//    (
//        SELECT
//            Sum([t3].[answers_unread])
//        FROM
//            [topic_info] [t3]
//        WHERE
//            [f].[ID] = [t3].[gid]
//    ) as [c5],
//    (
//        SELECT
//            Sum([t4].[answers_me_unread])
//        FROM
//            [topic_info] [t4]
//        WHERE
//            [f].[ID] = [t4].[gid]
//    ) as [c7]
//FROM
//    [server_forums] [f]
//WHERE
//    [f].[ID] = @id1
                #endregion
                if (queryable.Count() > 0)
                {
                    var forum = queryable.Single();
                    ID = forum.ID;
                    Name = forum.Name;
                    ForumDescription = forum.Descript;

                    MessagesCount = forum.AnswersCount + forum.TopicCount;
                    Unread = forum.Unread;
                    RepliesToMeUnread = forum.MeUnread;
#if FRM_UPDATE_TRACE
                    Trace.WriteLine(
                        string.Format(
                            "FRM:Forum LoadData:RepliesToMe Unread {0}, Unread {1}, summary {2}", //MLHIDE
                            RepliesToMeUnread,
                            Unread,
                            MessagesCount));
#endif
                }
            }
        }

        #endregion

        /// <summary>
        /// Creates the instance.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        //public static Forum CreateInstance(IServiceProvider provider)
        //{
        //    // TODO:Do we need Forum CreateInstance(IServiceProvider provider)?
        //    //return TypeAccessor<Forum>.CreateInstance();
        //    return new Forum(provider);
        //}

        /// <summary>
        /// Gets a value indicating whether this instance is virtual.
        /// Получает значение, указывающее, является ли этот форум виртуальным
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is virtual; otherwise, <c>false</c>.
        /// </value>
        public virtual bool IsVirtual
        {
            get
            {
                return false;
            }
        }

        protected int ActiveMsgId
        {
            get
            {
                return _activeMsgId;
            }

            private set 
            { 
                _activeMsgId = value; 
            }
        }

        protected IMsg ActiveMsgTarget
        {
            get
            {
                return (IMsg) _activeMsg.Target;
            }
            set
            {
                _activeMsg.Target = value;
                if (value != null)
                {
                    ActiveMsgId = value.ID;
                }
                else
                {
                    ActiveMsgId = -1;
                }
            }
        }

        #region Работа с БД.

        /// <summary>
        /// Loads the data for forum.
        /// </summary>
        /// <param name="forumId">The forum id.</param>
        internal void LoadData(int forumId) 
        {
            using (var db = _serviceProvider.CreateDBContext())
            {
                var forum =
                        db
                                .ServerForums(f => f.ID == forumId)
                                .Select(
                                f =>
                                new
                                {
                                    f.ID,
                                    f.Name,
                                    f.Descript,
                                    MsgCount = f.TopicInfos.Sum(ti => ti.AnswersCount),
                                    Unread = f.TopicInfos.Sum(ti => ti.AnswersUnread),
                                    MeUnread = f.TopicInfos.Sum(ti => ti.AnswersToMeUnread)
                                })
                                .SingleOrDefault();

                if (forum != null)
                {
                    ID = forum.ID;
                    Name = forum.Name;
                    ForumDescription = forum.Descript;
                    MessagesCount = forum.MsgCount;
                    Unread = forum.Unread;
                    RepliesToMeUnread = forum.MeUnread;
                }
            }
        }

        #endregion

    }
}